package com.fowo.api.user.model;

import cn.hutool.core.util.StrUtil;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.Data;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.util.Date;
import java.util.Map;
import java.util.TreeMap;

/**
 * 表示 Jwt 中保存的用户信息，可以在控制器方法参数中使用，将自动提取到登录用户的基本信息
 */
@Data
@Component
public class JwtUserInfo implements Serializable {
    public static final String HeaderName = "Authorization";

    public static final String ClaimsDisplayName = "dn";

    /**
     * 来宾用户类型
     */
    public static final String TYPE_GUEST = "guest";

    private static final String TokenHeader = "Bearer ";
    private static final String BasicHeader = "Basic ";
    private static final String secret = "Secret forProject,EveryOneChangSomething";


    private static RedisTemplate<String, Object> redisTemplate;

    @Resource
    public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
        JwtUserInfo.redisTemplate = redisTemplate;
    }

    public static boolean checkToken(String token) {
        JwtUserInfo jwtUserInfo = from(token);
        if (jwtUserInfo != null && (jwtUserInfo.getExpiration() == null || jwtUserInfo.getExpiration().after(new Date()))) {
            return true;
        }
        return false;
    }

    /**
     * 获取当前头部 token (不验证有效性)
     */
    public static String getToken() {
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (servletRequestAttributes != null) {
            HttpServletRequest request = servletRequestAttributes.getRequest();
            String authorization = request.getHeader(HeaderName);
            if (StrUtil.isNotEmpty(authorization)) {
                return authorization;
            }
        }
        return null;
    }

    //构造用户(仅限内部调用)
    private static ThreadLocal<JwtUserInfo> tempJwtUserInfoLocal = new ThreadLocal<>();

    /**
     * 直接配置当前用户，在配置后所有的 JwtUserInfo.fromHeader 都会获取此值
     * @apiNote 使用时必须使用 try finally 结构，在用完后 setCurrent(null) 否则会造成用户权限引入到其它访问中，更安全的方法是使用实例提供的 {@link #runAs(Runnable)}
     * @param userInfo 要配置的前用户信息，或 null 清除之前配置的当前用户
     * @deprecated 更安全的方法是使用实例提供的 {@link #runAs(Runnable)}
     */
    @Deprecated(since = "2023-08-18")
    public static void setCurrent(JwtUserInfo userInfo) {
        tempJwtUserInfoLocal.set(userInfo);
    }
    public static void setGuestCurrent() {
        final JwtUserInfo jwtUserInfo = new JwtUserInfo();
        jwtUserInfo.setType(TYPE_GUEST);
        jwtUserInfo.setUsername("guest");
        jwtUserInfo.setUserId(2L);
        jwtUserInfo.setExpiration(DateUtils.addMinutes(new Date(), 30));
        tempJwtUserInfoLocal.set(jwtUserInfo);
    }
    public static JwtUserInfo fromHeader() {
        String token = getToken();
        if (StringUtils.hasText(token)) {
            JwtUserInfo jwtUserInfo = from(token);
            if (jwtUserInfo != null && (jwtUserInfo.getExpiration() == null || jwtUserInfo.getExpiration().after(new Date()))) {
                return jwtUserInfo;
            }
        }
       JwtUserInfo tmpJwtUserInfo = tempJwtUserInfoLocal.get();
        if (tmpJwtUserInfo != null) {
            return tmpJwtUserInfo;
        }
        return null;
    }

    public static JwtUserInfo from(String jwt) {
        try {
            if (jwt.startsWith(TokenHeader)) {
                jwt = jwt.substring(TokenHeader.length());
            }
            JwtUserInfo jwtUserInfo = new JwtUserInfo();
            jwtUserInfo.claims = new TreeMap<>();
            final Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(jwt).getBody();
            for (Map.Entry<String, Object> claim : claims.entrySet()) {
                if (Claims.SUBJECT.equals(claim.getKey())) {
                    jwtUserInfo.username = String.valueOf(claim.getValue());
                } else if (Claims.EXPIRATION.equals(claim.getKey())) {
                    jwtUserInfo.setExpiration(claims.getExpiration());
                } else if (Claims.ISSUED_AT.equals(claim.getKey())) {
                    //
                } else if ("type".equals(claim.getKey())) {
                    jwtUserInfo.setType(String.valueOf(claim.getValue()));
                } else if ("uid".equals(claim.getKey())) {
                    jwtUserInfo.setUserId(toLong(claim.getValue()));
                } else {
                    jwtUserInfo.claims.put(claim.getKey(), claim.getValue());
                }
            }
            return jwtUserInfo;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    private static Long toLong(Object num) {
        if (num instanceof Number) {
            return ((Number) num).longValue();
        }
        if (num instanceof String) {
            return Long.parseLong((String) num);
        }
        return null;
    }

    /**
     * 创建一个表示来宾身份的 token, 不关联任何个人数据
     */
    public static String createGuestToken() {
        final JwtUserInfo jwtUserInfo = new JwtUserInfo();
        jwtUserInfo.setType(TYPE_GUEST);
        jwtUserInfo.setUsername("guest");
        jwtUserInfo.setUserId(2L);
        jwtUserInfo.setExpiration(DateUtils.addMinutes(new Date(), 30));
        return jwtUserInfo.toJwt();
    }

    /**
     * 用户类别，可用于区别前台用户与后台用户
     */
    private String type;
    /**
     * 用户名
     */
    private String username;
    /**
     * 用户主键编号
     */
    private Long userId;
    /**
     * 过期时间
     */
    private Date expiration;

    /**
     * 其它信息
     */
    private Map<String, Object> claims = new TreeMap<>();

    public String getDisplayName() {
        if (claims.containsKey(ClaimsDisplayName)) {
            return (String) claims.get(ClaimsDisplayName);
        }
        return username;
    }

    public void setDisplayName(String displayName) {
        claims.put(ClaimsDisplayName, displayName);
    }

    /**
     * 获取编码后的 Jwt
     */
    public String toJwt() {
        JwtBuilder jwtBuilder = Jwts.builder()
                .setSubject(username)
                .setExpiration(expiration)
                .setIssuedAt(new Date());
        if (claims.size() > 0) {
            for (Map.Entry<String, Object> entry : claims.entrySet()) {
                jwtBuilder.claim(entry.getKey(), entry.getValue());
            }
        }
        return TokenHeader + jwtBuilder
                .claim("type", type)
                .claim("uid", userId)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    /**
     * 使用当前身份运行代码块
     * @param runnable 要使用当前身份运行的代码块
     */
    public void runAs(Runnable runnable) {
        JwtUserInfo oldTempJwtUser = tempJwtUserInfoLocal.get();
        tempJwtUserInfoLocal.set(this);
        try {
            runnable.run();
        }
        finally {
            tempJwtUserInfoLocal.set(oldTempJwtUser);
        }
    }


}
